package com.unistec.fileserver.server.controller;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.activation.MimetypesFileTypeMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageInfo;
import com.hnit.auth.client.annotation.IgnoreClientToken;
import com.hnit.auth.client.annotation.IgnoreUserToken;
import com.hnit.common.context.BaseContextHandler;
import com.hnit.common.util.DateUtils;
import com.unistec.fileserver.client.entity.Resource;
import com.unistec.fileserver.server.TempFile;
import com.unistec.fileserver.server.UploadFactory;
import com.unistec.fileserver.server.biz.FileServerService;
import com.unistec.fileserver.server.enums.FileType;
import com.unistec.fileserver.server.provider.IResourceSearchProvider;
import com.unistec.fileserver.server.provider.IStorageProvider;
import com.unistec.fileserver.server.provider.IUploadChain;
import com.unistec.fileserver.server.provider.IUploadChainProvider;
import com.unistec.fileserver.server.provider.ftp.FTPStorageProvider;
import com.unistec.framework.exception.NotifyException;
import com.unistec.framework.utils.FileUtils;
import com.unistec.framework.utils.JsonUtils;
import com.unistec.framework.utils.Objects;
import com.unistec.framework.utils.ReturnResult;
import com.unistec.framework.utils.UUIDUtils;
import com.unistec.taskcenter.client.annotation.DistributedTask;

@IgnoreClientToken
@IgnoreUserToken
@RestController
@RequestMapping("/manager")
public class FileServerController {
	Logger logger = LoggerFactory.getLogger(FileServerController.class);

	ExecutorService uploadThreadService = null;

	// 20个线程并发执行上传
	Object uploadThreadServiceLock = Executors.newFixedThreadPool(10);

	@Autowired
	private HttpServletRequest request;

	@Autowired
	HttpServletResponse response;

	@Autowired
	UploadFactory uploadFactory;

	@Autowired
	FileServerService fileServerService;

	/**
	 * 每一份上传资源都将被保存到文件库，并标识为临时文件，直到获取通知成为真正资源后，方入库。所有临时资源都将会被定期清理
	 * 
	 * @description
	 * @author 胡晓光
	 * @date 2021-9-29
	 * @time 9:47:41
	 * 
	 * @param file
	 * @param asyncUpload
	 * @param uploadId
	 * @return
	 */
	@RequestMapping(value = "/uploadFile", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
	public JSONObject uploadFile(@RequestPart("file") MultipartFile file, @RequestParam(name = "asyncUpload", required = false) Boolean asyncUpload, @RequestParam(name = "folder", required = false) String folder,
			@RequestParam(name = "fileName", required = false) String fileName, @RequestParam(name = "isTempFile", required = false) Boolean isTempFile,
			@RequestParam(name = "uploadId", required = false) String uploadId) {

		if (Objects.isNull(uploadId)) {
			uploadId = UUIDUtils.get32UUID();
		}
		if (Objects.isNull(isTempFile)) {
			isTempFile = false;
		}
		if (Objects.isNull(asyncUpload)) {
			asyncUpload = false;
		}

		JSONObject jo = new JSONObject();
		jo.put("success", false);
		jo.put("resultCode", -1);

		try {
			Resource meta = doUpload(asyncUpload, file, "PrivateUnOrginazedResource", folder, null, false, isTempFile, uploadId);

			jo = JsonUtils.bean2JSONObject(meta);
			jo.put("success", true);
			jo.put("resultCode", 0);
			jo.put("data", meta);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return jo;
	}

	/**
	 * 上传文件统一接口
	 * 
	 * @description
	 * @author 胡晓光
	 * @date 2021-9-29
	 * @time 9:48:00
	 * 
	 * @param file
	 * @param folder
	 * @param needWatermark
	 * @param resourceType
	 * @param filenameSufix
	 * @param asyncUpload
	 * @param uploadId      什么是uploadId？这里有必要要说明一下。uploadId是用于在前端生成一个表单号，后端去接收这个表单号。当发现这个表单号并不是后端所需要的表单号，则判定当前表单无效。
	 *                      同时，uploadId也是用于在异步上传时，快速给前端返回当前上传结果的。uploadId也是一次上传的唯一标识。通过uploadId可以直接查找到所上传的资源。一对一的关系。因为
	 *                      一次异步执行立刻结束了，线程在上传时还没有保存到数据库，是无法关联到哪次上传的。而前端又需要关联到所上传的资源。
	 * @return
	 */
	@RequestMapping(value = "/upload", method = RequestMethod.POST)
	public JSONObject upload(@RequestParam("file") MultipartFile file, // 要上传的文件
			@RequestParam(name = "folder", required = false) String folder, // 需要保存到哪个目录中
			@RequestParam(name = "needWatermark", required = false) Boolean needWatermark, // 是否添加水印
			@RequestParam(name = "resourceType", required = false) String resourceType, // 资源类型
			@RequestParam(name = "filenameSufix", required = false) String filenameSufix, // 自定义文件后缀
			@RequestParam(name = "asyncUpload", required = false) Boolean asyncUpload, // 是否异步上传
			@RequestParam(name = "uploadId", required = false) String uploadId // 是否启用异步上传，上传完毕后直接完成，后续过程不管

	) {
		String upId = Objects.isNull(uploadId) ? UUIDUtils.get32UUID() : uploadId;
		final boolean isTempFile;
		if (Objects.nonNull(request.getHeader("isTemp"))) {
			isTempFile = Boolean.parseBoolean(request.getHeader("isTemp"));
		} else if (Objects.isNull(request.getParameter("isTemp"))) {
			isTempFile = Boolean.parseBoolean(request.getParameter("isTemp"));
		} else {
			isTempFile = true;
		}

		boolean remark = Objects.isNull(needWatermark) ? false : needWatermark.booleanValue();
		Resource meta = doUpload(asyncUpload != null && asyncUpload.booleanValue() == true, file, resourceType, folder, filenameSufix, remark, isTempFile, upId);

		JSONObject jo = JsonUtils.bean2JSONObject(meta);
		jo.put("success", true);
		jo.put("resultCode", 0);
		jo.put("data", meta);
		return jo;
	}

	@RequestMapping(value = "/download", method = RequestMethod.POST, consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
	public void genFeginProcessDiagram(HttpServletResponse httpServletResponse, @RequestParam("unique") String unique) throws Exception {
		// 处理旧数据的兼容问题，这里要判定是否FTP
		IResourceSearchProvider resourceSearchProvider = uploadFactory.getResourceSearchProvider();
		
		Resource resource = fileServerService.getResourceInfoByUnique(unique);
		if (resource == null && !(resourceSearchProvider instanceof FTPStorageProvider)) {
			throw new NotifyException("未找到文件");
		}

		InputStream in = resource == null ? resourceSearchProvider.getFileInputStream(unique) : resourceSearchProvider.getFileInputStream(resource);

		try (OutputStream out = httpServletResponse.getOutputStream();) {
			IOUtils.copy(in, httpServletResponse.getOutputStream());
		}
	}

	/**
	 * 下载文件资源
	 * 
	 * @description
	 * @author 胡晓光
	 * @date 2021-9-29
	 * @time 9:49:11
	 * 
	 * @param asyncUpload
	 * @param file
	 * @param resourceType
	 * @param folder
	 * @param filenameSufix
	 * @param needWatermark
	 * @param isTemp
	 * @param uploadId
	 * @param storageType
	 * @return
	 */
	Resource doUpload(boolean asyncUpload, MultipartFile file, String resourceType, String folder, String filenameSufix, boolean needWatermark, boolean isTemp, String uploadId) {
		if (null == file) {
			throw new NotifyException("请选择有效资源！");
		}

		String userId = request.getParameter("uid");

		userId = Objects.isNull(userId) ? BaseContextHandler.getUserID() : userId;
		String userName = BaseContextHandler.getUsername();
		String orgId = BaseContextHandler.getUserOrgId();

		String orginalFileName = file.getOriginalFilename();
		if (Objects.nonNull(orginalFileName)) {
			if (orginalFileName.contains("&")) {
				throw new NotifyException("您上传的文件名称中包含了特殊字符，请修改文件名，去除不合法的特殊字符（如：'%'，'&'，'='）后再上传！");
			}
		}

		// 开始一个上传的链式生命流程
		IUploadChainProvider chainProvider = uploadFactory.getUploadChainProvider();
		IUploadChain chain = chainProvider.start();

		// 采用异步方式进行上传，以释放线程，这里不做等待

		// 检查是否需要保存临时文件
		final TempFile tempFile;
		try {
			tempFile = TempFile.createTempFile(file);

			// 如果文件后缀参数存在，则将文件替换为带后缀的文件名
			if (Objects.nonNull(filenameSufix)) {
				File newFile = FileUtils.renameFileExtentionName(tempFile.getFile(), filenameSufix);
				tempFile.setFile(newFile);
				tempFile.setOriginalFilename(newFile.getName());
			}
		} catch (Exception e) {
			logger.error("文件上传失败", e);
			throw new NotifyException("文件上传失败");
		}

		chain.cached();

		// 获取文件类型
		FileType fileType = UploadFactory.getUploadFileType(file.getOriginalFilename());

		// 获取存储提供者
		final IStorageProvider storageProvider = uploadFactory.getStoragetProvider();

		JSONObject paramsHolder = createParamsHolder(request, userId, orgId, fileType, uploadId);

		Resource meta = null;

		// 启用异步上传
		if (asyncUpload == true) {
			meta = new Resource();
			meta.setUploadId(uploadId);
			meta.setUploadBeginTime(new Date());
			meta.setOrgId(orgId);
			meta.setFilenameSuffix(FileUtils.getUriExtention(tempFile.getOriginalFilename()));
			meta.setMimeType(tempFile.getContentType());
			meta.setUploadStatus(0);
			meta.setName(tempFile.getOriginalFilename());
			meta.setConvertStatus(0);
			meta.setContentType(tempFile.getContentType());
			meta.setId(uploadId);
			meta.setUrl(uploadId);
			if (Objects.isNull(meta.getForeignIdentity())) {
				meta.setForeignIdentity(uploadId);
			}
			meta.setSize(file.getSize());
			meta.setOrgId(orgId);
			meta.setUploadUserId(userId);
			meta.setUploadUserName(userName);

			fileServerService.insert(meta);

			// 上传完毕，通知
			chain.uploadFinished();
			chain.end(meta);

			paramsHolder.put("asyncUpload", true);
			paramsHolder.put("resourceId", meta.getId());

			// 采用异步上传方式
			executeRunnable(new Runnable() {
				public void run() {
					try {
						storageProvider.uploadFile(paramsHolder, folder, tempFile.getFile(), tempFile.getOriginalFilename());
					} catch (Exception e) {
						logger.error("文件上传失败", e);
					}
				}
			});
		} else {
			// 采用同步上传方式
			// 采用存储进行上传，如果保存了临时文件，则上传临时文件；如果没有保存临时文件，则上传martipart
			Date beginUploadTime = new Date();
			try {
				meta = storageProvider.uploadFile(paramsHolder, folder, tempFile.getFile(), tempFile.getOriginalFilename());
			} catch (Exception e) {
				if (e instanceof NotifyException) {
					throw (NotifyException) e;
				} else {
					logger.error("文件上传失败", e);
					throw new NotifyException("上传失败");
				}
			}

			meta.setFilenameSuffix(FileUtils.getUriExtention(tempFile.getOriginalFilename()));
			meta.setContentType(tempFile.getContentType());
			meta.setIsTemp(isTemp);
			meta.setUploadId(uploadId);
			meta.setUploadBeginTime(beginUploadTime);
			meta.setUploadFinishTime(new Date());
			meta.setName(tempFile.getOriginalFilename());
			meta.setMimeType(new MimetypesFileTypeMap().getContentType(meta.getName()));
			meta.setOrgId(orgId);
			meta.setUploadUserId(userId);
			meta.setUploadUserName(userName);

			if (Objects.isNull(meta.getSize())) {
				meta.setSize(tempFile.getSize());
			}

			chain.uploadFinished();
			chain.end(meta);

			if (Objects.isNull(meta.getUrl()) && Objects.nonNull(meta.getForeignIdentity())) {
				meta.setUrl(meta.getForeignIdentity());
			}
			fileServerService.insert(meta);
		}

		return meta;
	}

	/**
	 * 通过唯一值获取资源
	 * 
	 * @description
	 * @author 胡晓光
	 * @date 2021-9-29
	 * @time 9:58:30
	 * 
	 * @param params
	 * @return
	 */
	@RequestMapping(value = "/getResourceInfoByUnique")
	public ReturnResult<Resource> getResourceInfoByUnique(@RequestParam("unique") String unique) {
		Resource resource = fileServerService.getResourceInfoByUnique(unique);
		return ReturnResult.ok(resource);
	}

	/**
	 * 搜索资源
	 * 
	 * @param params
	 * @return
	 */
	@RequestMapping("/searchResources")
	public ReturnResult<List<Resource>> searchResources(@RequestBody JSONObject params) {
		IResourceSearchProvider resourceSearchProvider = uploadFactory.getResourceSearchProvider();
		PageInfo<Resource> pageInfo = new PageInfo<>();
		pageInfo.setPageNum(params.getInteger("pageNum"));
		pageInfo.setPageSize(params.getInteger("pageSize"));
		List<Resource> resources = resourceSearchProvider.searchResource(params, pageInfo);
		return ReturnResult.ok(resources, pageInfo);
	}

	/**
	 * 清除超过24小时未用的临时文件
	 * 
	 * @description
	 * @author 胡晓光
	 * @date 2021-9-29
	 * @time 9:58:48
	 * 
	 * @param body
	 * @return
	 */
	@DistributedTask(jobGroupId = "FileServer", jobId = "cleanTempFilesOver24Hours", jobName = "清除超过24小时未用的临时文件", requestUrl = "http://fileserver/manager/cleanTempFilesOver24Hours", executeTime = "03:00:00")
	@PostMapping("/cleanTempFilesOver24Hours")
	public ReturnResult<Void> cleanTempFilesOver24Hours(@RequestBody JSONObject body) {
		fileServerService.deleteResourceOlderThan24Hours();
		return ReturnResult.ok();
	}

	JSONObject createParamsHolder(HttpServletRequest request, String userId, String orgId, FileType fileType, String uploadId) {
		JSONObject jo = new JSONObject();

		if (jo.containsKey("id")) {
			jo.remove("id");
		}

		if (request != null) {
			Enumeration<String> headerNames = request.getHeaderNames();
			while (headerNames.hasMoreElements()) {
				String key = headerNames.nextElement();
				jo.put(key, request.getHeader(key));
			}

			Enumeration<String> parameterNames = request.getParameterNames();
			while (parameterNames.hasMoreElements()) {
				String key = parameterNames.nextElement();
				jo.put(key, request.getParameter(key));
			}
		}

		if (Objects.nonNull(orgId)) {
			jo.put("corpId", orgId);
		}

		if (Objects.nonNull(userId)) {
			jo.put("creatorId", userId);
		}
		if (Objects.nonNull(fileType)) {
			jo.put("fileType", fileType.name());
		}

		String sourceUrl = request.getParameter("sourceUrl");
		if (Objects.nonNull(sourceUrl)) {
			jo.put("sourceUrl", sourceUrl);
		}

		if (Objects.nonNull(uploadId)) {
			jo.put("uploadId", uploadId);
		}
		return jo;
	}

	/**
	 * 下载文件
	 * 
	 * @description
	 * @author 胡晓光
	 * @date 2021-9-29
	 * @time 10:00:45
	 * 
	 * @param url
	 * @param filename
	 */
	@RequestMapping("/filedownload")
	public void download(@RequestParam("unique") String unique, @RequestParam(name = "filename", required = false) String filename, @RequestParam(name = "isAttachement", required = false) Boolean isAttachement)
			throws Exception {
		if (Objects.isNull(unique)) {
			throw new NotifyException("未找到文件");
		}

		IResourceSearchProvider resourceSearchProvider = uploadFactory.getResourceSearchProvider();

		// 批量文件下载压缩包
		if (unique.contains(",") || unique.contains("|")) {
			// 多文件打包压缩下载
			String[] array = unique.contains(",") ? unique.split(",") : unique.contains("|") ? unique.split("\\|") : new String[] { unique };
			Resource resource;
			File tempFile;
			List<File> files = new ArrayList<>();
			List<Resource> resources = new ArrayList<>();
			for (String u : array) {
				resource = fileServerService.getResourceInfoByUnique(u);
				if (resource == null && !(resourceSearchProvider instanceof FTPStorageProvider))
					continue;

				try (InputStream in = resource == null ? resourceSearchProvider.getFileInputStream(unique) : resourceSearchProvider.getFileInputStream(resource)) {
					tempFile = FileUtils.createTempFile(resource.getFilenameSuffix());
					FileUtils.saveInputStream(in, tempFile);
					files.add(tempFile);
					resources.add(resource);
				}
			}

			if (Objects.isNull(files)) {
				throw new NotifyException("没有需要压缩的文件");
			}

			// 打包并下载文件
			response.setContentType("application/octet-stream;charset=ISO8859-1");
			String attachmentName = Objects.isNull(filename) ? DateUtils.getReqLongDate() + ".zip" : new String(filename.getBytes(), "ISO8859-1") + ".zip";
			response.addHeader("Content-Disposition", "attachment;fileName=" + attachmentName);// 设置文件名

			File zipFile = zipCompress(files, resources);
			OutputStream out = null;
			InputStream in = null;
			try {
				out = response.getOutputStream();
				in = new FileInputStream(zipFile);
				IOUtils.copy(in, out);
			} finally {
				if (in != null) {
					try {
						in.close();
					} catch (Exception e) {

					}
				}

				if (out != null) {
					try {
						out.close();
					} catch (Exception e) {

					}
				}

				if (Objects.nonNull(files)) {
					for (File f : files) {
						FileUtils.deleteFile(f);
					}
				}
				if (zipFile != null && zipFile.exists())
					FileUtils.deleteFile(zipFile);
			}

		} else {
			// 单文件下载
			Resource resource = fileServerService.getResourceInfoByUnique(unique);
			if (resource == null && !(resourceSearchProvider instanceof FTPStorageProvider)) {
				throw new NotifyException("未找到文件");
			}

			if (isAttachement == null) {
				isAttachement = true;
			}

			if (isAttachement) {
				String newFileName = filename;
				if (Objects.isNull(newFileName) && resource != null) {
					newFileName = resource.getName();
				}
				if (Objects.isNull(newFileName)) {
					newFileName = DateUtils.getReqLongDate();
				}
				newFileName = new String(newFileName.getBytes(), "ISO8859-1");

				response.setContentType("application/octet-stream;charset=ISO8859-1");
				response.addHeader("Content-Disposition", "attachment;fileName=" + newFileName);// 设置文件名
			}

			try (OutputStream out = response.getOutputStream(); InputStream in = resource == null ? resourceSearchProvider.getFileInputStream(unique) : resourceSearchProvider.getFileInputStream(resource)) {
				IOUtils.copy(in, out);
			}
		}
	}

	String getFileNameFromUrl(String url) {
		int index = url.lastIndexOf("/");
		String filename = url.substring(index);
		if (filename.contains("?")) {
			index = filename.indexOf("?");
			filename = filename.substring(0, index);
		}
		return filename;
	}

	/**
	 * 执行上传任务
	 * 
	 * @description
	 * @author 胡晓光
	 * @date 2021-9-29
	 * @time 16:15:54
	 * 
	 * @param runnable
	 */
	public void executeRunnable(Runnable runnable) {
		uploadThreadService.execute(runnable);
	}

	public static File zipCompress(List<File> files, List<Resource> resource) throws Exception {
		if (Objects.isNull(files)) {
			throw new NotifyException("没有需要压缩的文件");
		}

		File tempZipFile = FileUtils.createTempFile(".zip");
		try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tempZipFile))) {
			byte[] bufs = new byte[1024 * 10];

			for (int i = 0; i < files.size(); i++) {
				ZipEntry zipEntry = new ZipEntry(resource.get(i).getName());
				zos.putNextEntry(zipEntry);
				try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(files.get(i)), 1024 * 10)) {
					int read = 0;
					while ((read = bis.read(bufs, 0, 1024 * 10)) != -1) {
						zos.write(bufs, 0, read);
					}
				}
			}
		}
		return tempZipFile;
	}
}
